package com.basker.pisces.orm.database;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

import org.apache.commons.io.IOUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.basker.pisces.core.datasource.DbInfo;
import com.basker.pisces.core.datasource.IDataSourceManager;
import com.basker.pisces.exception.ExceptionFactory;
import com.basker.pisces.orm.data.meta.DataSetMeta;
import com.basker.pisces.orm.data.meta.FieldMeta;
import com.basker.pisces.orm.data.meta.IndexMeta;
import com.basker.pisces.orm.data.meta.TableMeta;
import com.basker.pisces.orm.parser.ISqlParser;
import com.basker.pisces.orm.session.ISqlSession;
import com.basker.pisces.orm.session.ISqlSessionFactory;
import com.basker.pisces.orm.session.SqlSessionFactoryManager;

/**
 * 负责数据库表结构SQL
 *
 * @author hangwen
 */
public class DatabaseDefinitionSqlGenerator {

    private static final String LINE_SEPARATOR = System.getProperty("line.separator");

    private DataSetMeta dataSetMeta;

    public DatabaseDefinitionSqlGenerator(DataSetMeta dataSetMeta) {
        this.dataSetMeta = dataSetMeta;
    }

    /**
     * 在指定文件夹下生成表sql文件，一个表一个文件
     *
     * @param dir
     */
    @SuppressWarnings("deprecation")
    public void generateTableSql2Dir(File dir) {
        Assert.isTrue(dir.isDirectory(), "'dir' " + dir.getName() + " must be a directory");

        List<FileWriter> tempWriters = new ArrayList<>();

        Function<String, Writer> writerFunction = name -> {
            File file = new File(dir, name + ".sql");
            try {
                FileWriter writer = new FileWriter(file);
                tempWriters.add(writer);

                return writer;
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        };

        try {
            this.generate0(writerFunction);
        } finally {
            for (FileWriter fileWriter : tempWriters) {
                IOUtils.closeQuietly(fileWriter);
            }
        }
    }

    /**
     * 往目标文件生成schema sql
     *
     * @param file
     * @param append
     */
    public void generateSchemaSql2File(File file, boolean append) {
        Assert.isTrue(!file.isDirectory(), "'file' " + file.getName() + " must be a file");

        try (FileWriter writer = new FileWriter(file, append)) {
            try (ISqlSession session = this.getSqlSession()) {
                DbInfo dbInfo = this.getDbInfo(this.dataSetMeta.getSchema());
                this.generateSchema(dbInfo, session, writer);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 往目标文件生成表sql
     *
     * @param file
     * @param appen
     */
    public void generateTableSql2File(File file, boolean append) {
        Assert.isTrue(!file.isDirectory(), "'file' " + file.getName() + " must be a file");

        try (FileWriter writer = new FileWriter(file, append)) {
            this.generate0(t -> writer);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void generate0(Function<String, Writer> writerFunction) {
        Writer writer = null;

        try (ISqlSession session = this.getSqlSession()) {
            for (TableMeta tableMeta : this.dataSetMeta.getTableMetas()) {
                writer = writerFunction.apply(tableMeta.getName());
                Assert.notNull(writer, "'writerFunction' has no writer");

                this.generateTable(tableMeta, session, writer);

                for (TableMeta subTableMeta : tableMeta.getSubTableMetas()) {
                    writer = writerFunction.apply(subTableMeta.getName());
                    Assert.notNull(writer, "'writerFunction' has no writer");

                    this.generateTable(subTableMeta, session, writer);
                }
            }
        } catch (Exception e) {
            throw ExceptionFactory.createRuntimeException(e);
        }
    }

    private void generateSchema(DbInfo dbInfo, ISqlSession session, Writer writer) throws IOException {
        ISqlParser sqlParser = session.getSqlParser();

        writer.write("-- create schema " + dbInfo.getDatabaseName());
        writer.write(LINE_SEPARATOR);
        writer.write(sqlParser.getCreateSchemaSql(dbInfo, session));
        writer.write(LINE_SEPARATOR);
        writer.write(LINE_SEPARATOR);
    }

    private void generateTable(TableMeta tableMeta, ISqlSession session, Writer writer) throws IOException {
        ISqlParser sqlParser = session.getSqlParser();

        writer.write("-- create table " + tableMeta.getName());
        writer.write(LINE_SEPARATOR);
        writer.write(sqlParser.getCreateTableSql(tableMeta));
        writer.write(LINE_SEPARATOR);
        writer.write(LINE_SEPARATOR);

        List<FieldMeta> fieldMetas = new ArrayList<>(tableMeta.getFieldMetas());
        if (tableMeta.isChildTable()) {
            fieldMetas.add(tableMeta.getFkFieldMeta());
        }

        for (FieldMeta fieldMeta : fieldMetas) {
            writer.write("-- syn field " + fieldMeta.getName());
            writer.write(LINE_SEPARATOR);
            writer.write(sqlParser.getSynFieldSql(tableMeta, fieldMeta));
            writer.write(LINE_SEPARATOR);
            writer.write(LINE_SEPARATOR);
        }

        List<IndexMeta> indexMetas = tableMeta.getIndexMetas();
        if (!CollectionUtils.isEmpty(indexMetas)) {
            for (IndexMeta indexMeta : indexMetas) {
                writer.write("-- syn index " + indexMeta.getName());
                writer.write(LINE_SEPARATOR);
                writer.write(sqlParser.getSynIndexSql(tableMeta, indexMeta));
                writer.write(LINE_SEPARATOR);
                writer.write(LINE_SEPARATOR);
            }
        }

    }

    private DbInfo getDbInfo(String schema) {
        IDataSourceManager dataSourceManager = IDataSourceManager.getInstance();

        if (StringUtils.isEmpty(schema)) {
            return dataSourceManager.getDefaultDataSourceInfo();
        }

        DbInfo dataSourceInfo = dataSourceManager.getDataSourceInfo(schema);
        if (dataSourceInfo == null) {
            throw new IllegalArgumentException("parameter 'schema':" + schema + " is not found.");
        }

        return dataSourceInfo;
    }

    private ISqlSession getSqlSession() {
        ISqlSessionFactory factory = SqlSessionFactoryManager.getDfaultFactory();
        ISqlSession session = null;

        String schema = this.dataSetMeta.getSchema();
        if (StringUtils.isEmpty(schema)) {
            session = factory.createSession();
        } else {
            session = factory.createSession(schema);
        }

        return session;

    }
}
